package org.intellij.plugins.junit4.listeners;

import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.refactoring.listeners.RefactoringElementListener;
import org.apache.log4j.Logger;
import org.intellij.plugins.junit4.ClassNameResolver;
import org.intellij.plugins.junit4.JavaFile;

import javax.swing.*;

/**
 * Listens for renames and moves. If a class is a production class and has a corresponding
 * test class, this listener will ask the user if they would like to move/rename th test
 * as well.
 *
 * @author Justin Tomich
 */
public class RenameListener implements RefactoringElementListener
{
    private static final String RENAME_MSG = "Ok to rename corresponding test class?";
    private static final String RENAME_TITLE = "Rename Test Class";

    private static final String MOVE_MSG = "Ok to move corresponding test class?";
    private static final String MOVE_TITLE = "Move Test Class";

    private final Logger log = Logger.getLogger("CONSOLE-WARN");

    /**
     * project the targetFile belongs to
     */
    private final Project project;

    /**
     * file being renamed or moved
     */
    private final JavaFile targetFile;

    /**
     * resolver for the project
     */
    private final ClassNameResolver resolver;

    // try to find this based upon "targetFile"
    private final JavaFile testFile;


    public RenameListener(Project project,
                          JavaFile targetFile,
                          ClassNameResolver resolver)
    {
        this.project = project;
        this.targetFile = targetFile;
        this.resolver = resolver;

        testFile = getTestFile(targetFile);
        log.warn("RenameListener constructed: " + this);
    }

    public void elementMoved(PsiElement newElement)
    {
        log.warn("RenameListener.elementMoved() original=" + targetFile + " testFile=" +
                testFile + " newElement=" + newElement);
        if (notElligible(newElement)) return;

        String newQualifiedClassname = targetFile.getQualifiedClassName();
        String newPackage = resolver.packageOnly(newQualifiedClassname);

        log.warn("newPackage=" + newPackage);
        boolean ok = askUserOkMove(newPackage);
        if (!ok) return;
        testFile.changePackage(newPackage);
    }

    public void elementRenamed(PsiElement newElement)
    {
        log.warn("RenameListener.elementRenamed() targetFile=" + targetFile +
                " testFile=" + testFile);
        if (notElligible(newElement)) return;

        String newClassName = targetFile.getQualifiedClassName();
        log.warn("newClassName=" + newClassName);

        String newTestQualifiedName = resolver.getTestClassName(newClassName);
        String newTestName = resolver.nameOnly(newTestQualifiedName);
        log.warn("newTestName=" + newTestName);

        boolean ok = askUserOkRename(newTestName);
        if (!ok) return;

        testFile.rename(newTestName);
    }

    /**
     * Get the corresponding test class for the given file. If file is
     * itself a test class, return null. If file is a production class but no test
     * corresponding test file exists, return null.
     *
     * @param file get test targetFile for the given JavaFile
     * @return JavaFile test class for targetFile, if one exists
     */
    private JavaFile getTestFile(JavaFile file)
    {
        log.warn("RenameListener.getTestFile(" + file.getQualifiedClassName() +
                " isTest()=" + file.isTest());
        log.warn("hasCorrespondingTest()=" + file.hasCorrespondingTest());

        if (file.isTest()) return null;
        if (!file.hasCorrespondingTest()) return null;
        return file.toggleProductionTestFiles();
    }

    private boolean askUserOkMove(String newPackage)
    {
        final String oldName = resolver.nameOnly(testFile.getQualifiedClassName());
        String msg = MOVE_MSG + " " + oldName + " will be moved to package " + newPackage;
        return askUserOk(msg, MOVE_TITLE);
    }

    private boolean askUserOkRename(String newTestName)
    {
        final String oldName = resolver.nameOnly(testFile.getQualifiedClassName());
        String msg = RENAME_MSG + " " + oldName + " will be renamed " + newTestName;
        return askUserOk(msg, RENAME_TITLE);
    }

    private boolean askUserOk(String msg, String title)
    {
        Icon icon = Messages.getQuestionIcon();
        int ok = Messages.showYesNoDialog(project, msg, title, icon);
        return ok == DialogWrapper.OK_EXIT_CODE;
    }

    private boolean notElligible(PsiElement newElement)
    {
        if (!(newElement instanceof PsiClass)) return true;
        PsiClass psiClass = (PsiClass) newElement;
        if (isInnerClass(psiClass)) return false;
        if (testFile == null) return true;
        return targetFile.isTest();
    }

    private boolean isInnerClass(PsiClass psiClass)
    {
        final String name = psiClass.getName();
        if (name == null) return false;
        final PsiFile file = psiClass.getContainingFile();
        final String fileName = file.getName();
        return (fileName.indexOf(name) < 0);
    }


    public String toString()
    {
        return "RenameListener{" +
                "targetFile=" + targetFile +
                ", testFile=" + testFile + '}';
    }
}

